/*
 * cubeTests.h
 *
 * Test functions for the cube class
 *
 *  Created on: Sep 27, 2013
 * @author Daniel Rogers
 *
 */

#ifndef CUBE_TESTS_H_
#define CUBE_TESTS_H_

#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include "testCommon.h"

//TODO: Add cubeIO cube loading tests

bool cubeTestEquals();
bool cubeTestUnitConst();
bool cubeTestEncodeDecode();
bool cubeTestTopRotation();
bool cubeTestBottomRotation();
bool cubeTestLeftRotation();
bool cubeTestRightRotation();
bool cubeTestFrontRotation();
bool cubeTestBackRotation();
bool cubeTestPermute();
bool topLeftRotation();
bool topRightRotation();
bool topBackRotation();
bool topFrontRotation();
bool testCompression();
//This is more out of curiosity and isn't a rigorous test of performance
bool testThroughput();

/*
 * Execute entire suite of tests for the cube
 */
bool cubeTestAll()
{
    bool passed = true;

    passed &= cubeTestEquals();
    passed &= cubeTestEncodeDecode();
    passed &= cubeTestTopRotation();
    passed &= cubeTestBottomRotation();
    passed &= cubeTestLeftRotation();
    passed &= cubeTestRightRotation();
    passed &= cubeTestFrontRotation();
    passed &= cubeTestBackRotation();
    passed &= cubeTestPermute();
    passed &= topLeftRotation();
    passed &= topRightRotation();
    passed &= topBackRotation();
    passed &= topFrontRotation();
    passed &= cubeTestUnitConst();
    passed &= testCompression();

    return passed;
}

/*
 * Test equality operator
 */
bool cubeTestEquals()
{
    Cube base;
    Cube working;

    getUnitCube(&base);
    getUnitCube(&working);

    bool passed = assertTestPass(&base, &working, "Equality Test");
    if (!passed)
    {
        puts("Equality test failed, cannot continue");
        exit(-1);
    }

    return true;
}

/*
 * test UNIT_CUBE constant value
 */
bool cubeTestUnitConst()
{
    Cube base;
    getUnitCube(&base);

    bool passed = assertTestPass(&base, &UNIT_CUBE, "Const def test");

    return passed;
}

/*
 * Test encode/decode
 */
bool cubeTestEncodeDecode()
{
    Cube base;
    Cube working;

    getUnitCube(&base);
    getUnitCube(&working);

    uint8_t corners[8];
    uint8_t edges[12];

    decodeCorners(working.corners, corners);
    decompressEdges(working.edges, edges);

    encodeCorners(corners, &working.corners);
    compressEdges(edges, working.edges);

    bool passed = assertTestPass(&base, &working, "Encode/Decode");

    /*
     * Test shuffling by 1 position
     */
    //corners[0] == 1, corners[1] == 0
    uint8_t temp = corners[1];
    corners[1] = corners[0];
    corners[0] = temp;

    encodeCorners(corners, &working.corners);
    decodeCorners(working.corners, corners);

    temp = corners[1];
    corners[1] = corners[0];
    corners[0] = temp;

    encodeCorners(corners, &working.corners);
    passed &= assertTestPass(&base, &working, "Shuffle by 1");

    /*
     * Test shuffling by 3 positions
     */
    getUnitCube(&working);
    decodeCorners(working.corners, corners);
    //corners[0] == 3, corners[3] == 0
    temp = corners[3];
    corners[3] = corners[0];
    corners[0] = temp;

    encodeCorners(corners, &working.corners);
    decodeCorners(working.corners, corners);

    temp = corners[3];
    corners[3] = corners[0];
    corners[0] = temp;

    encodeCorners(corners, &working.corners);
    passed &= assertTestPass(&base, &working, "Shuffle by 3");

    /*
     * Test shuffling 2 positions by 1 position
     */
    getUnitCube(&working);
    decodeCorners(working.corners, corners);
    //corners[0] == 1, corners[1] == 2, corners[2] == 0
    temp = corners[0];
    corners[0] = corners[1];
    corners[1] = corners[2];
    corners[2] = temp;

    encodeCorners(corners, &working.corners);
    decodeCorners(working.corners, corners);

    temp = corners[2];
    corners[2] = corners[1];
    corners[1] = corners[0];
    corners[0] = temp;

    encodeCorners(corners, &working.corners);
    passed &= assertTestPass(&base, &working, "Shuffle 2 by 1");

    /*
     * Test shuffling 2 positions by 1 position version 2
     */
    getUnitCube(&working);
    decodeCorners(working.corners, corners);
    //corners[1] == 2, corners[2] == 3, corners[3] == 1
    temp = corners[1];
    corners[1] = corners[2];
    corners[2] = corners[3];
    corners[3] = temp;

    encodeCorners(corners, &working.corners);
    decodeCorners(working.corners, corners);

    temp = corners[3];
    corners[3] = corners[2];
    corners[2] = corners[1];
    corners[1] = temp;

    encodeCorners(corners, &working.corners);
    passed &= assertTestPass(&base, &working, "Shuffle 2 by 1 v2");

    if (!passed)
    {
        puts("Encode/Decode tests failed, cannot continue");
        exit(-1);
    }

    return true;
}

/*
 * Test top rotation
 *
 * 4 turns should reset faces
 */
bool cubeTestTopRotation()
{
    Cube base;
    Cube working;
    bool passed;

    getUnitCube(&base);
    getUnitCube(&working);
    //test full rotation
    //top
    working = rotateTop(working);
    working = rotateTop(working);
    working = rotateTop(working);
    working = rotateTop(working);
    passed = assertTestPass(&base, &working, "Top Rotation");

    //Manually set up a quarter turn from a base cube
    uint8_t c_turned[8] =
    { 3, 0, 1, 2, 4, 5, 6, 7 };
    uint8_t e_turned[12] =
    { 3, 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11 };

    //corner spin unchanged from base
    encodeCorners(c_turned, &base.corners);
    compressEdges(e_turned, base.edges);

    working = rotateTop(working);

    passed &= assertTestPass(&base, &working, "Top quarter-rotation");

    return passed;
}

//bottom
bool cubeTestBottomRotation()
{
    Cube base;
    Cube working;
    bool passed;

    getUnitCube(&base);
    getUnitCube(&working);

    working = rotateBottom(working);
    working = rotateBottom(working);
    working = rotateBottom(working);
    working = rotateBottom(working);
    passed = assertTestPass(&base, &working, "Bottom Rotation");

    //Manually set up a quarter turn from a base cube
    uint8_t c_turned[8] =
    { 0, 1, 2, 3, 5, 6, 7, 4 };
    uint8_t e_turned[12] =
    { 0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 8 };

    //corner spin unchanged from base
    encodeCorners(c_turned, &base.corners);
    compressEdges(e_turned, base.edges);

    working = rotateBottom(working);

    passed &= assertTestPass(&base, &working, "Bottom quarter-rotation");

    return passed;
}

//left
bool cubeTestLeftRotation()
{
    Cube base;
    Cube working;
    bool passed;

    getUnitCube(&base);
    getUnitCube(&working);
    working = rotateLeft(working);
    working = rotateLeft(working);
    working = rotateLeft(working);
    working = rotateLeft(working);

    passed = assertTestPass(&base, &working, "Left rotation");

    //Manually set up a quarter turn from a base cube
    //from {0, 1, 2, 3, ## 4, 5, 6, 7}
    uint8_t c_turned[8] =
    { 0, 2, 6, 3, 4, 1, 5, 7 };
    //from {0, 1, 2, 3, ## 4, 5, 6, 7, ## 8, 9, 10, 11}
    uint8_t e_turned[12] =
    { 0, 6, 2, 3, 4, 1, 9, 7, 8, 5, 10, 11 };

    //corner spin changes to be {0, 1, 1, 0, 0, 1, 1, 0}
    //                          00 01 01 00 00 01 01 00
    //                          |   1|   4 |   1 |   4
    uint16_t spin = 0x1414;
    encodeCorners(c_turned, &base.corners);
    compressEdges(e_turned, base.edges);
    base.corner_spin = spin;

    working = rotateLeft(working);

    passed &= assertTestPass(&base, &working, "Left quarter-rotation");

    return passed;
}

//right
bool cubeTestRightRotation()
{
    Cube base;
    Cube working;
    bool passed;

    getUnitCube(&base);
    getUnitCube(&working);

    working = rotateRight(working);
    working = rotateRight(working);
    working = rotateRight(working);
    working = rotateRight(working);
    passed = assertTestPass(&base, &working, "Right Rotation");

    //Manually set up a quarter turn from a base cube
    //from {0, 1, 2, 3, ## 4, 5, 6, 7}
    uint8_t c_turned[8] =
    { 4, 1, 2, 0, 7, 5, 6, 3 };
    //from {0, 1, 2, 3, ## 4, 5, 6, 7, ## 8, 9, 10, 11}
    uint8_t e_turned[12] =
    { 0, 1, 2, 4, 11, 5, 6, 3, 8, 9, 10, 7 };

    //corner spin changes to be {1, 0, 0, 1, 1, 0, 0, 1}
    //                          01 00 00 01 01 00 00 01
    //                          |   4 |   1 |   4 |   1
    uint16_t spin = 0x4141;
    encodeCorners(c_turned, &base.corners);
    compressEdges(e_turned, base.edges);
    base.corner_spin = spin;

    working = rotateRight(working);

    passed &= assertTestPass(&base, &working, "Right quarter-rotation");

    return passed;
}

//front
bool cubeTestFrontRotation()
{
    Cube base;
    Cube working;
    bool passed;

    getUnitCube(&base);
    getUnitCube(&working);

    working = rotateFront(working);
    working = rotateFront(working);
    working = rotateFront(working);
    working = rotateFront(working);
    passed = assertTestPass(&base, &working, "Front Rotation");

    //Manually set up a quarter turn from a base cube
    //from {0, 1, 2, 3, ## 4, 5, 6, 7}
    uint8_t c_turned[8] =
    { 0, 1, 3, 7, 4, 5, 2, 6 };
    //from {0, 1, 2, 3, ## 4, 5, 6, 7, ## 8, 9, 10, 11}
    uint8_t e_turned[12] =
    { 0, 1, 7, 3, 4, 5, 2, 10, 8, 9, 6, 11 };

    //corner spin changes to be {2, 2, 0, 0, 2, 2, 0, 0}
    //                          10 10 00 00 10 10 00 00
    //                          |   A |   0 |   A |   0
    uint16_t spin = 0xA0A0;
    encodeCorners(c_turned, &base.corners);
    compressEdges(e_turned, base.edges);
    base.corner_spin = spin;

    working = rotateFront(working);

    passed &= assertTestPass(&base, &working, "Front quarter-rotation");

    return passed;
}

//back
bool cubeTestBackRotation()
{
    Cube base;
    Cube working;
    bool passed;

    getUnitCube(&base);
    getUnitCube(&working);

    working = rotateBack(working);
    working = rotateBack(working);
    working = rotateBack(working);
    working = rotateBack(working);
    passed = assertTestPass(&base, &working, "Back Rotation");

    //Manually set up a quarter turn from a base cube
    //from {0, 1, 2, 3, ## 4, 5, 6, 7}
    uint8_t c_turned[8] =
    { 1, 5, 2, 3, 0, 4, 6, 7 };
    //from {0, 1, 2, 3, ## 4, 5, 6, 7, ## 8, 9, 10, 11}
    uint8_t e_turned[12] =
    { 5, 1, 2, 3, 0, 8, 6, 7, 4, 9, 10, 11 };

    //corner spin changes to be {0, 0, 2, 2, 0, 0, 2, 2}
    //                          00 00 10 10 00 00 10 10
    //                          |   0 |   A |   0 |   A
    uint16_t spin = 0x0A0A;
    encodeCorners(c_turned, &base.corners);
    compressEdges(e_turned, base.edges);
    base.corner_spin = spin;

    working = rotateBack(working);

    passed &= assertTestPass(&base, &working, "Back quarter-rotation");

    return passed;
}

/*
 * Test permute all
 */
bool cubeTestPermute()
{
    Cube base;
    Cube working;
    Cube perms[6];

    getUnitCube(&base);
    getUnitCube(&working);

    permuteAll(working, perms);

    working = rotateTop(working);
    bool passed = assertTestPass(&working, &perms[0], "Permute all - Top");

    getUnitCube(&working);
    working = rotateBottom(working);
    passed &= assertTestPass(&working, &perms[1], "Permute all - Bottom");

    getUnitCube(&working);
    working = rotateLeft(working);
    passed &= assertTestPass(&working, &perms[2], "Permute all - Left");

    getUnitCube(&working);
    working = rotateRight(working);
    passed &= assertTestPass(&working, &perms[3], "Permute all - Right");

    getUnitCube(&working);
    working = rotateFront(working);
    passed &= assertTestPass(&working, &perms[4], "Permute all - Front");

    getUnitCube(&working);
    working = rotateBack(working);
    passed &= assertTestPass(&working, &perms[5], "Permute all - Back");

    return passed;
}

bool topLeftRotation()
{
    Cube base;
    Cube working;
    bool passed = true;

    getUnitCube(&base);
    getUnitCube(&working);

    working = rotateTop(working);
    working = rotateLeft(working);
    working = rotateLeft(working);
    working = rotateLeft(working);
    working = rotateLeft(working);
    working = rotateTop(working);
    working = rotateTop(working);
    working = rotateTop(working);

    //Should be reset
    passed &= assertTestPass(&base, &working, "Rotate top, left, reset");

    return passed;
}

bool topRightRotation()
{
    Cube base;
    Cube working;
    bool passed = true;

    getUnitCube(&base);
    getUnitCube(&working);

    working = rotateTop(working);
    working = rotateRight(working);
    working = rotateRight(working);
    working = rotateRight(working);
    working = rotateRight(working);
    working = rotateTop(working);
    working = rotateTop(working);
    working = rotateTop(working);

    //Should be reset
    passed &= assertTestPass(&base, &working, "Rotate top, Right, reset");

    return passed;
}

bool topBackRotation()
{
    Cube base;
    Cube working;
    bool passed = true;

    getUnitCube(&base);
    getUnitCube(&working);

    working = rotateTop(working);
    working = rotateBack(working);
    working = rotateBack(working);
    working = rotateBack(working);
    working = rotateBack(working);
    working = rotateTop(working);
    working = rotateTop(working);
    working = rotateTop(working);

    //Should be reset
    passed &= assertTestPass(&base, &working, "Rotate top, Back, reset");

    return passed;
}

bool topFrontRotation()
{
    Cube base;
    Cube working;
    bool passed = true;

    getUnitCube(&base);
    getUnitCube(&working);

    working = rotateTop(working);
    working = rotateFront(working);
    working = rotateFront(working);
    working = rotateFront(working);
    working = rotateFront(working);
    working = rotateTop(working);
    working = rotateTop(working);
    working = rotateTop(working);

    //Should be reset
    passed &= assertTestPass(&base, &working, "Rotate top, Front, reset");

    return passed;
}

bool testCompression()
{
    Cube initial;
    Cube initperms[18];
    Cube final[18];
    CompressedCubeSet intermed[3];
    bool passed = true;

    getUnitCube(&initial);
    permuteAllFaces(initial, initperms);

    for (uint8_t i = 0; i < 18; ++i)
    {
        compressCube(&initperms[i], &intermed[i / CUBES_PER_SET],
                i % CUBES_PER_SET);
    }

    for (uint8_t i = 0; i < 18; ++i)
    {
        decompressCube(&intermed[i / CUBES_PER_SET], i % CUBES_PER_SET,
                &final[i]);
        passed &= assertTestPass(&initperms[i], &final[i],
                "Rotate all compress, decompress");
    }

    return passed;
}

bool testThroughput()
{
    //Number of permutations to run through
    const int TEST_SIZE = 1000000;

    //Timers
    clock_t tstart, tend;
    double tex;

    //State storage
    Cube working;
    Cube perms[6];

    getUnitCube(&working);

    srand(time(NULL));

    tstart = clock();
    for (int i = 0; i < TEST_SIZE; ++i)
    {
        permuteAll(working, perms);
        working = perms[rand() % 6];
    }
    tend = clock();

    tex = (double) (tend - tstart) / CLOCKS_PER_SEC;

    printf("Cube w/corners: %d\n", working.corners);
    printf("    Time (s): %.4f\n", tex);
    //remember that pAll gives ALL possible face changes for a state
    printf("    pAll calls/second: %.0f\n", TEST_SIZE / tex);

    return true;

}

#endif

